using System.Collections.Generic;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;

public enum MeshType
{
    XYZ,
    XY,
    XZ
}

[RequireComponent(typeof(MeshFilter))]
public class MeshBuilder : MonoBehaviour
{
    private readonly static Color DebugSphereColor = new Color(1, 0.25f, 0);
    private const float SmallestValidTriangleArea = 0.01f;

    [Header("Debug Options")]
    [SerializeField]
    private bool drawDebugVertices = false;

    [SerializeField]
    private bool drawDebugLabels = false;

    [SerializeField]
    private bool printDebugInfo = false;

    [SerializeField]
    private float debugSphereRadius = 0.1f;

    [SerializeField]
    private MeshType meshType = MeshType.XYZ;

    //Use property instead to ensure initialization
    private MeshFilter meshFilter;

    //We keep this as a variable so we can draw debug Info
    private List<Vector3> vertices;

    private GUIStyle vertexLabelStyle;


    [HideInInspector]
    [SerializeField]
    protected GeometryDebug meshDebug;

    protected MeshFilter MeshFilter
    {
        get
        {
            if (meshFilter == null)
            {
                meshFilter = GetComponent<MeshFilter>();
                //cannot be null since MeshFilter is required. 
            }

            return meshFilter;
        }
    }

    protected List<Vector3> GetVertices() { return vertices; }

    Mesh mesh;

    public void UpdateMesh()
    {
        if (meshDebug == null)
        {
            meshDebug = new GeometryDebug();
        }

        meshDebug.Clear();

        meshFilter = MeshFilter;
        DestroyOldMesh();
        Preprocess();

        if (mesh == null) mesh = new Mesh();
        else mesh.Clear();

        vertices = CalculateVertices();
        if (vertices == null || vertices.Count <= 0)
        {
            return;
        }

        DebugLog("Vertices", vertices.Count);
        mesh.SetVertices(vertices);

        var triangles = CalculateTriangles();


        DebugLog("Triangles", triangles.Count);
        mesh.SetTriangles(triangles, 0);

        //We set triangles before doing this test so that the number of 
        //vertex indices and their range can be checked before we execute
        //the code below, that will 
        ValidateTriangleAreas(triangles);

        var uvs = CalculateUvs(vertices);

        if (uvs == null)
        {
            //can be null if subclass does not support texturing
            DebugLog("Uvs", null);
        }
        else
        {
            DebugLog("Uvs", uvs.Count);
            mesh.SetUVs(0, uvs);
        }

        var normals = CalculateNormals();

        if (normals == null)
        {
            //can be null if subclass does override default normals
            DebugLog("Normals", null);
            mesh.RecalculateNormals();
        }
        else
        {
            DebugLog("Normals", normals.Count);
            mesh.SetNormals(normals);
        }

        mesh.RecalculateBounds();
        meshFilter.sharedMesh = mesh;


    }

    private void ValidateTriangleAreas(List<int> triangles)
    {
        for (int i = 0; i < triangles.Count / 3; i++)
        {
            int vertexIndexA = triangles[3 * i];
            int vertexIndexB = triangles[3 * i + 1];
            int vertexIndexC = triangles[3 * i + 2];
            var vertexA = vertices[vertexIndexA];
            var vertexB = vertices[vertexIndexB];
            var vertexC = vertices[vertexIndexC];

            float area = MeshBuilderUtils.AreaOfTriangle(vertexB - vertexA, vertexC - vertexA);

            if (area < SmallestValidTriangleArea)
            {
                Debug.LogWarning(
                   string.Format(
                      "Triangle is too small. <{0}, {1}, {2}>, <{3}, {4}, {5}>",
                      vertexIndexA, vertexIndexB, vertexIndexC, vertexA, vertexB, vertexC
                   ));
            }
        }
    }

    public void OnDrawGizmos()
    {
        if (!drawDebugVertices && !drawDebugLabels) return;

        if (vertices == null) return; //can happen if no mesh was ever created. 

        //if(vertexLabelStyle == null)
        {
            vertexLabelStyle = new GUIStyle();
            vertexLabelStyle.normal.textColor = Color.white;
            vertexLabelStyle.alignment = TextAnchor.MiddleCenter;
        }

        if (drawDebugVertices)
        {
            meshDebug.Radius = debugSphereRadius;

            for (int i = 0; i < vertices.Count; i++)
            {
                var vertex = vertices[i];
                var spherePosition = transform.TransformPoint(vertex);
                float radius = transform.lossyScale.magnitude * debugSphereRadius;

                GeometryDebug.DrawDot(spherePosition, radius, DebugSphereColor, meshType);
            }

            if (meshDebug != null)
            {
                meshDebug.Draw(transform, meshType);
            }
        }
#if UNITY_EDITOR
        if (drawDebugLabels)
        {
            for (int i = 0; i < vertices.Count; i++)
            {
                var vertex = vertices[i];
                var spherePosition = transform.TransformPoint(vertex);
                float radius = transform.lossyScale.magnitude * debugSphereRadius;

                Handles.Label(spherePosition + Vector3.up * radius / 2, i.ToString(), vertexLabelStyle);
            }
        }
#endif
    }

    virtual protected List<Vector3> CalculateVertices()
    {
        return MeshBuilderUtils.QuadVertices();
    }

    virtual protected List<Vector2> CalculateUvs(List<Vector3> vertices)
    {
        return MeshBuilderUtils.GetStandardUvsXY(vertices, true, true);
    }

    virtual protected void Preprocess() { }

    virtual protected List<int> CalculateTriangles()
    {
        return new List<int>
      {
         0, 3, 1,
         1, 3, 2
      };
    }

    virtual protected List<Vector3> CalculateNormals()
    {
        return null;
    }

    [ContextMenu("Update Mesh")]
    protected void UpdateMeshTest()
    {
        UpdateMesh();
    }

    private void DestroyOldMesh()
    {
        //if (MeshFilter.sharedMesh != null)
        //{
        //   if (Application.isPlaying)
        //   {
        //      Destroy(MeshFilter.sharedMesh); //prevents memory leak
        //   }
        //   else
        //   {
        //      DestroyImmediate(MeshFilter.sharedMesh);
        //   }
        //}
    }

    protected void DebugLog(string label, object message)
    {
        if (!printDebugInfo) return;

        if (message == null)
        {
            DebugLog(label, "null");
        }
        else
        {
            Debug.Log(label + ": " + message.ToString(), this);
        }
    }

    protected void DebugAddDotXY(Vector3 position, GLColor color)
    {
        meshDebug.AddDotXY(position, color);
    }

    public void DebugAddArrow(Vector3 position, Vector3 direction, GLColor color)
    {
        meshDebug.AddArrow(position, direction, color);
    }

    private void GetVerticesFromMesh()
    {
        if (MeshFilter.sharedMesh != null)
        {
            vertices = MeshFilter.sharedMesh.vertices.ToList();

            DebugLog("Vertices", "Refreshed from mesh.");
        }
    }
#if UNITY_EDITOR
    private void OnValidate()
    {
        //if(vertices == null)
        //{
        //   GetVerticesFromMesh();
        //}

        UpdateMesh();
    }
#endif
}